/*
 * Copyright 2006 - Chris Maki
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.agoracms.model;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

// Table name set in the orm-mapping.xml file
@Entity
@NamedQueries({
	@NamedQuery(name="forum.findByName", query="select f from Forum f where f.name = ?1")
})
public class Forum extends ModelBase implements Serializable {

    @Column(unique=true)
    private String name;

    @Lob
    @Column(nullable=false)
    private String description;

    @Column(name="pruning_enabled")
    private Boolean pruningEnabled = false;

    @Enumerated(value=EnumType.ORDINAL)
    private Status status = Status.UNLOCKED;

    // TODO this field needs an explaination of what it is for
    @Column(precision=8, scale=2)
    private BigDecimal averages;
    
    @Column(name="pruning_interval")
    private Integer pruningInterval;
    
    /**
     * Determins how old a post needs to be before it is automatically pruned (deleted)
     */
    @Column(name="prune_posts_days_old")
    private Integer prunePostsDaysOld;
    
    /**
     * Allows users to manually set the order forumns are displayed in
     */
    @Column(name="forum_order")
    private Integer forumOrder;
    
    /**
	 * Convenience counter to let you know how many posts there are for this
	 * forum - this could be calculated when fetching the forum record from the
	 * db as well
	 */
    @Column(name="forum_post_count")
    private Integer forumPostCount;
    
    /**
	 * Convenience counter to let you know how many topics there are for this
	 * forum - this could be calculated when fetching the forum record from the
	 * db as well
	 */
    @Column(name="forum_topic_count")
    private Integer forumTopicCount;
    
    /**
     * The default type is FTPType.NORMAL 
     */
    @Enumerated(EnumType.ORDINAL)
    FTPType type = FTPType.NORMAL;

    /**
     * The type of forum.  Category is a user defined classification
     */
    // TopLinkFix
    // @ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.PERSIST}) // defaults are okay, fk column will be category_id
    @ManyToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST}) // defaults are okay, fk column will be category_id
    Category category;
    
    /**
     * The member responsible for managing the content of this form
     * TODO this might need to be not-null
     */
    @ManyToOne // defaults are okay
    User moderator;
    
//    /**
//     * The last post to this forum
//     */
    // TopLinkFix
    // @ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.PERSIST})
    @ManyToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})
    Post lastPost;
    
    @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE}, mappedBy="forum")
    @OrderBy("type asc, dateUpdated desc")
    Set<Topic> topics = new HashSet<Topic>();

    private static final long serialVersionUID = 7718784906645387871L;

    public BigDecimal getAverages() {
        return averages;
    }

    public void setAverages(BigDecimal averages) {
        this.averages = averages;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isPruningEnabled() {
        return pruningEnabled;
    }

    public void setPruningEnabled(boolean pruningEnabled) {
        this.pruningEnabled = pruningEnabled;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }


	public Integer getForumOrder() {
		return forumOrder;
	}

	public void setForumOrder(Integer forumOrder) {
		this.forumOrder = forumOrder;
	}

	public Integer getForumPostCount() {
		if (forumPostCount == null) {
			forumPostCount = new Integer(0);
		}
		return forumPostCount;
	}

	public void setForumPostCount(Integer forumPostCount) {
		this.forumPostCount = forumPostCount;
	}

	public Integer getForumTopicCount() {
		if (forumTopicCount == null) {
			forumTopicCount = new Integer(0);
		}
		return forumTopicCount;
	}

	public void setForumTopicCount(Integer forumTopicCount) {
		this.forumTopicCount = forumTopicCount;
	}

	public Integer getPrunePostsDaysOld() {
		return prunePostsDaysOld;
	}

	public void setPrunePostsDaysOld(Integer prunePostsDaysOld) {
		this.prunePostsDaysOld = prunePostsDaysOld;
	}

	public Integer getPruningInterval() {
		return pruningInterval;
	}

	public void setPruningInterval(Integer pruningInterval) {
		this.pruningInterval = pruningInterval;
	}

	public Category getCategory() {
		return category;
	}

	public void setCategory(Category category) {
		this.category = category;
	}

	public FTPType getType() {
		return type;
	}

	public void setType(FTPType type) {
		this.type = type;
	}

	public User getModerator() {
		return moderator;
	}

	public void setModerator(User moderator) {
		this.moderator = moderator;
	}

	public Post getLastPost() {
		return lastPost;
	}

	public void setLastPost(Post lastPost) {
		// manage both sides of the relationship
		lastPost.setForum(this);
		this.lastPost = lastPost;
	}

	public Set<Topic> getTopics() {
		return topics;
	}

	public void setTopics(Set<Topic> topics) {
		this.topics = topics;
	}

	public boolean addTopic(Topic topic) {
		boolean addSuccessful = topics.add(topic);
		
		// only increment if we successfully added the topic to our set of
		// topics
		if (addSuccessful) {
			incrementTopicCount();
			// manage both sides of the relationship
			topic.setForum(this);

		}
		return addSuccessful;
		
	}
	private void incrementTopicCount() {
		int count = getForumTopicCount();
		setForumTopicCount(++count);
	}

	public boolean addPostToTopic(Post post, Topic topic) {
		boolean addSuccessful = topic.addPost(post);
		if (addSuccessful) {
			// manage both sides of the relationship (since it is bi-directional)
			this.lastPost = post;
			post.setForum(this);
			incrementPostCount();			
		}
		return addSuccessful;
	}
	
	public void addPostToParent(Post p, Post parentPost) {
		// TODO we might want to do some book-keeping when a parent is attached to a
		// child, like check to see if the child has an existing parent. If so,
		// if the existing parent associated with this forum, if so we do not
		// want to increment the post count. If it is a different forum,
		// subtract one from the other forum then update ours. If parent is
		// null, increment our count
		
		// for now, just increment post count
		incrementPostCount();		
		p.setParent(parentPost);
		
	}

	// TODO this might become a derrived value, perform a count during the
	// select?
	private void incrementPostCount() {
		int count = getForumPostCount();
		setForumPostCount(++count);
		
	}

	public boolean remove(Topic arg0) {
		return topics.remove(arg0);
	}
	
    @Override
    public String toString() {
    	// TODO complete this list :-)
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
        .append("id", getId())
        .append("name", getName())
        .append("description", getDescription())
        .toString();
    }

	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Forum))
			return false;
		final Forum other = (Forum) obj;
		if (name == null) {
			if (other.getName() != null)
				return false;
		} else if (!name.equals(other.getName()))
			return false;
		return true;
	}

}
